"""
VotingClassifier（投票分类器）算法应用示例

这个示例展示了如何使用VotingClassifier结合多个基础分类器进行集成学习，
以提高预测性能和稳定性。

VotingClassifier支持两种投票方式：
1. 硬投票（hard voting）：选择预测类别最多的类别
2. 软投票（soft voting）：选择预测概率平均值最高的类别
"""

import pandas as pd
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.ensemble import VotingClassifier, RandomForestClassifier, GradientBoostingClassifier
from sklearn.svm import SVC
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
import warnings
warnings.filterwarnings('ignore')

# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号

class voting_classifier():
    # 额外的实用函数
    def quick_voting_classifier(X, y, models=None, voting='soft', test_size=0.2, save_model=True, model_path='voting_classifier_model.pkl', scaler_path='voting_classifier_scaler.pkl'):
        """
        快速使用VotingClassifier的函数，支持模型保存
        
        参数：
        X: 特征数据
        y: 标签数据
        models: 自定义模型列表，如果为None则使用默认模型
        voting: 投票方式 ('hard' 或 'soft')
        test_size: 测试集比例
        save_model: 是否保存模型和scaler
        model_path: 模型保存路径
        scaler_path: scaler保存路径
        
        返回：
        voting_clf: 训练好的VotingClassifier
        scaler: 用于数据标准化的scaler对象
        """
        import joblib
        
        # 数据分割和标准化
        X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=test_size, random_state=42, stratify=y)
        scaler = StandardScaler()
        X_train_scaled = scaler.fit_transform(X_train)
        
        # 使用默认模型或自定义模型
        if models is None:
            models = [
                ('lr', LogisticRegression(random_state=42, max_iter=1000)),
                ('rf', RandomForestClassifier(n_estimators=100, random_state=42)),
                ('svm', SVC(probability=True, random_state=42)),
                ('gb', GradientBoostingClassifier(random_state=42))
            ]
        
        # 创建和训练VotingClassifier
        voting_clf = VotingClassifier(estimators=models, voting=voting)
        voting_clf.fit(X_train_scaled, y_train)
        
        # 保存模型和scaler
        if save_model:
            try:
                joblib.dump(voting_clf, model_path)
                joblib.dump(scaler, scaler_path)
                print(f"模型已保存到: {model_path}")
                print(f"标准化器已保存到: {scaler_path}")
            except Exception as e:
                print(f"保存模型时出错: {e}")
        
        # 返回训练好的模型和相关对象
        return voting_clf, scaler

    def predict_with_trained_model(model=None, scaler=None, new_data=None, return_probabilities=True, 
                                model_path=None, scaler_path=None):
        """
        使用VotingClassifier模型进行预测，支持直接传入模型对象或从文件路径加载模型
        
        参数：
        model: 训练好的VotingClassifier模型对象（可选）
        scaler: 数据标准化器对象（可选）
        new_data: 新数据（DataFrame或numpy数组）
        return_probabilities: 是否返回预测概率
        model_path: 模型文件路径（如果提供，将从文件加载模型）
        scaler_path: 标准化器文件路径（如果提供，将从文件加载标准化器）
        
        返回：
        predictions: 预测结果
        probabilities: 预测概率（如果return_probabilities=True且模型支持）
        model: 加载或使用的模型对象
        scaler: 加载或使用的标准化器对象
        """
        import joblib
        
        print("使用VotingClassifier模型进行预测...")
        
        # 从文件加载模型和scaler（如果提供了路径）
        loaded_model = model
        loaded_scaler = scaler
        
        if model_path is not None:
            try:
                print(f"从文件加载模型: {model_path}")
                loaded_model = joblib.load(model_path)
                print("模型加载成功")
            except Exception as e:
                print(f"加载模型时出错: {e}")
                raise
        
        if scaler_path is not None:
            try:
                print(f"从文件加载标准化器: {scaler_path}")
                loaded_scaler = joblib.load(scaler_path)
                print("标准化器加载成功")
            except Exception as e:
                print(f"加载标准化器时出错: {e}")
                raise
        
        # 验证模型和scaler是否可用
        if loaded_model is None:
            raise ValueError("请提供模型对象或模型文件路径")
        if loaded_scaler is None:
            raise ValueError("请提供标准化器对象或标准化器文件路径")
        
        # 验证新数据是否提供
        if new_data is None:
            raise ValueError("请提供新数据进行预测")
        
        # 数据预处理
        if isinstance(new_data, pd.DataFrame):
            new_data_scaled = loaded_scaler.transform(new_data)
        else:
            new_data_scaled = loaded_scaler.transform(new_data.reshape(1, -1) if new_data.ndim == 1 else new_data)
        
        # 进行预测
        predictions = loaded_model.predict(new_data_scaled)
        
        # 获取预测概率（如果模型支持）
        probabilities = None
        if return_probabilities:
            try:
                probabilities = loaded_model.predict_proba(new_data_scaled)
            except AttributeError:
                print("警告：模型不支持概率预测")
        
        return predictions, probabilities
